// JavaScript Document
(function() {
    
    var MODEL_DIALOG_LAYER = 10000;
    var MODEL_DIALOG_STEP = 5;
    var __manager = null;
       
    var UNIQUE_TIP_WINDOW_Z_INDEX = 1000000;
    
    var UI_WIDTH = CANVAS_WIDTH;
    var UI_HEIGHT = CANVAS_HEIGHT;
    
    var unique_tip_window = null;
    
    // data struct.
    function ImageFormat(url, offset, size)
    {
        if (this instanceof ImageFormat)
        {
            this.url = url;
            this.offset = new Point();
            this.size = null;
            if (offset != null)
            {
                this.offset.setValue(offset);
            }
            if (size != null)
            {
                this.size = size.clone();
            }
        }
        else
        {
            return new ImageFormat();
        }
    }
    
    function ImageRenderFormat(imgFormat, offset, size)
    {
        assert(imgFormat != null, "ImageRenderFormat: imgFormat cant be null.");
        if (this instanceof ImageRenderFormat)
        {
            this.imgFormat = imgFormat;
            this.offset = new Point();
            this.size = null;
            if (offset != null)
            {
                this.offset.setValue(offset);
            }
            if (size != null)
            {
                this.size = size.clone();
            }
        }
        else
        {
            return new ImageRenderFormat(imgFormat, offset, size);
        }
    }
    
    function Base(dom)
    {
        if (this instanceof Base)
        {
            ListenObject.call(this);
            if (dom == null)
            {
                this.dom = document.createElement('div');
                this.dom.style.position = "absolute";
            }
            else
            {
                this.dom = dom;
            }
        }
        else
        {
            return new Base(dom);
        }
    }
    
    inheritPrototype(Base, ListenObject);
    
    Base.MouseEvent = new Object();
    Base.MouseEvent.CLICK = "mouse_click";
    Base.MouseEvent.OVER = "mouse_over";
    Base.MouseEvent.LEAVE = "mouse_leave";
    
    Base.MOUSE_CLICK = 1;
    Base.MOUSE_OVER = 2;
    Base.MOUSE_LEAVE = 4;
    
    Base.prototype.setClassName = function(className)
    {
        this.dom.className = className;
    }
    
    Base.prototype.setScrollable = function()
    {
        this.dom.style.overflow = "auto";
    }
    
    Base.prototype.setClipForChildren = function()
    {
        this.dom.style.overflow = "hidden";
    }
    
    Base.prototype.stopMouseEventPropagation = function()
    {
        this.dom.onmousedown = function(e)
        {
            e.stopPropagation();
        }
        this.dom.onmousemove = function(e)
        {
            e.stopPropagation();
        }
        this.dom.onmouseup = function(e)
        {
            e.stopPropagation();
        }
        this.dom.onmouseout = function(e)
        {
            e.stopPropagation();
        }
    }
    
    Base.prototype.showTip = function(show, e)
    {
        // calculate tip position.
        var size = this.tipContent.getSize();
        var mouse_pos = getMousePointFromUIByMouseEvent(e);
        var margin = 5;
        var y = mouse_pos.y - size.y - margin;
        if (y < 0)
        {
            y = mouse_pos.y + margin + 20;
        }
        x = mouse_pos.x - size.x / 2;
        if (x + size.x > UI_WIDTH)
        {
            x = UI_WIDTH - size.x - margin;
        }
        else if (x < 0)
        {
            x = margin;
        }       
        __manager.showUniqueTip(show, this.tipContent, new Point(x, y));
    }
    
    Base.prototype.enableTextTip = function(text)
    {
        var tip = new TipContent();
        tip.string = text;
        this.enableTip(tip);
    }
    
    Base.prototype.enableTip = function(tipContent)
    {
        this.tipContent = tipContent;
        this.enableRawEvent(Base.MOUSE_OVER | Base.MOUSE_LEAVE);
        this.registerListener(Base.MouseEvent.OVER, curry_bind(this.showTip, this, true));
        this.registerListener(Base.MouseEvent.LEAVE, curry_bind(this.showTip, this, false));
    }
    
    Base.prototype.enableRawEvent = function(mask)
    {
        var saveThis = this;
        if (mask & Base.MOUSE_CLICK)
        {
            this.dom.onclick = function(e) {
                saveThis.updateListeners(Base.MouseEvent.CLICK, e);
            }
        }
        if (mask & Base.MOUSE_OVER)
        {
            this.dom.onmouseover = function(e) {
                saveThis.updateListeners(Base.MouseEvent.OVER, e);
            }
        }
        if (mask & Base.MOUSE_LEAVE)
        {
            this.dom.onmouseout = function(e) {
                saveThis.updateListeners(Base.MouseEvent.LEAVE, e);
            }
        }
    }
    
    Base.prototype.resize = function(rect)
    {
        with(this.dom.style)
        {
            if (rect)
            {
                left = rect.x + "px";
                top = rect.y + "px";
                width = rect.dx + "px";
                height = rect.dy + "px";
            }
            else
            {
                left = 0 + "px";
                top = 0 + "px";
                width = size.x + "px";
                height = size.y + "px";
            }
        }
    }
    
    Base.prototype.setBackgroundColor = function(color)
    {
        this.dom.style.backgroundColor = color;
    }
    
    Base.prototype.setPosition = function(pt)
    {
        this.dom.style.left = pt.x + 'px';
        this.dom.style.top = pt.y + 'px';
    }
    
    Base.prototype.setSize = function(size)
    {
        this.dom.style.width = size.x + 'px';
        this.dom.style.height = size.y + 'px';
    }
    
    Base.prototype.appendChild = function(child, zIndex)
    {
        if (zIndex)
        {
            child.setZIndex(zIndex);
        }
        assert(!child.dom.parentNode, "cant append node which has parent!");
        this.dom.appendChild(child.getDom());
    }
    
    Base.prototype.removeChild = function(child)
    {
        assert(child.dom.parentNode == this.dom, "cant remove child which is not this's child.");
        this.dom.removeChild(child.dom);
    }    
    
    Base.prototype.show = function(bShow)
    {
        if (bShow)
        {
            this.dom.style.display = 'block';
        }
        else
        {
            this.dom.style.display = 'none';
        }
    }
    
    Base.prototype.setZIndex = function(zIndex)
    {
        this.dom.style.zIndex = zIndex;
    }
    
    Base.prototype.getZIndex = function()
    {
        if (this.dom.style.zIndex == "")
        {
            return 0;
        }
        else
        {
            return parseInt(this.dom.style.zIndex);
        }
    }
    
    Base.prototype.getDom = function()
    {
        return this.dom;
    }
    
    Base.prototype.detach = function()
    {
        if (this.dom.parentNode)
        {
            this.dom.parentNode.removeChild(this.dom);
        }
    }
    
    function RenderImgItem(imgRenderFormat)
    {
        if (this instanceof RenderImgItem)
        {
            Base.call(this);
            this.dom.style.left = imgRenderFormat.offset.x + 'px';
            this.dom.style.top = imgRenderFormat.offset.y + 'px';
            this.dom.style.backgroundImage = "url(" + imgRenderFormat.imgFormat.url + ")";
            this.dom.style.backgroundRepeat = "no-repeat";
            this.dom.style.backgroundPosition = imgRenderFormat.imgFormat.offset.x + 'px ' + imgRenderFormat.imgFormat.offset.x + 'px';
            if (imgRenderFormat.size)
            {
                this.dom.style.width = imgRenderFormat.size.x + 'px';
                this.dom.style.height = imgRenderFormat.size.y + 'px';
            }
        }
        else
        {
            return new RenderImgItem(imgRenderFormat);
        }
    }
    
    inheritPrototype(RenderImgItem, Base);
    
    // define ToolBar and ToolBarItem. 
    function ImgToolBarItem(imgRenderFormat, size)
    {
        if (this instanceof ImgToolBarItem)
        {
            Base.call(this);
            this.setClassName("ui_img_tool_bar_item");
            this.enableRawEvent(Base.MOUSE_CLICK);
            if (!imgRenderFormat.size)
            {
                var imgSize = new Point(Math.min(size.x, size.x - imgRenderFormat.offset.x), Math.min(size.y, size.y - imgRenderFormat.offset.y));
                imgRenderFormat.size = imgSize;
            }
            this.img = new RenderImgItem(imgRenderFormat);
            this.appendChild(this.img);
            this.img.setZIndex(1);
            this.highlightImg = null;
            this.size = size;
            this.enableTip(TipContent.createStringContent("test tool bar item"));
        }
        else
        {
            return new ImgToolBarItem(size, imgRenderFormat);
        }
    }

    inheritPrototype(ImgToolBarItem, Base);
    
    ImgToolBarItem.prototype.addClickCallback = function(cb)
    {
        this.registerListener(this.mouseClickEvent, cb);
    }
    
    ImgToolBarItem.prototype.setHighlightImg = function(imgUrl, imgRect)
    {
        // add over and leave event in Base.
        this.enableRawEvent(Base.MOUSE_OVER | Base.MOUSE_LEAVE);
        this.registerListener(Base.MouseEvent.OVER, bind(this.onMouseOver, this));
        this.registerListener(Base.MouseEvent.LEAVE, bind(this.onMouseLeave, this));
    }
    
    ImgToolBarItem.prototype.highlight = function(highlight)
    {
        // do nothing now.
        log.info("enter Img highlight.");
    }
    
    ImgToolBarItem.prototype.onMouseOver = function(e)
    {
        this.highlight(true);
    }
    
    ImgToolBarItem.prototype.onMouseLeave = function(e)
    {
        this.highlight(false);
    }
    
    function ToolBar()
    {
        if (this instanceof ToolBar)
        {
            Base.call(this);
            this.setClassName("ui_tool_bar");
            this.items = [];
            this.width = 0;
            this.height = 0;
        }
        else
        {
            return new ToolBar();
        }
    }
    
    inheritPrototype(ToolBar, Base);
    
    ToolBar.prototype.bgColors = ['red', 'yellow', 'blue'];
    ToolBar.prototype.currentColorIndex = 0;
    ToolBar.prototype.getNextColor = function()
    {
        this.currentColorIndex++;
        return this.bgColors[this.currentColorIndex % this.bgColors.length];
    }
    
    ToolBar.prototype.addImgItem = function(imgRenderFormat, size, cb)
    {
        var barItem = new ImgToolBarItem(imgRenderFormat, size);
        barItem.resize(new Rect(this.width, 0, size.x, size.y));
        this.width += size.x;
        this.height = Math.max(this.height, size.y);
        if (cb)
        {
            barItem.registerListener(Base.MouseEvent.CLICK, cb);
        }
        this.appendChild(barItem);
        this.setSize(new Point(this.width, this.height));
        var totalColors = 
        barItem.setBackgroundColor(this.getNextColor());
        return barItem;
    }
    
    ToolBar.prototype.update = function(interval)
    {
        
    }   
    // define Button
    
    // define TipWindow.
    function TipContent()
    {
        if (this instanceof TipContent)
        {
            this.string = null;
        }
        else
        {
            return new TipContent();
        }
    }
    
    TipContent.createStringContent = function(string)
    {
        var content = new TipContent();
        content.string = string;
        return content;
    }
    
    TipContent.prototype.getSize = function()
    {
        // faked to get size from TipContent. maybe has better layout and function to get size.
        return new Point(this.string.length * 9, 48);
    }

    function TipWindow()
    {
        if (this instanceof TipWindow)
        {
            Base.call(this);
            this.content = null;
            this.rendered = false;
            this.active = false;
            this.timer = null;
            this.show(false);
        }
        else
        {
            return new TipWindow();
        }
    }
    inheritPrototype(TipWindow, Base);
    
    TipWindow.prototype.setContent = function(tipContent)
    {
        if (this.content != tipContent)
        {
            this.content = tipContent;
            this.rendered = false;
        }
    }
    
    TipWindow.prototype.onSetCallback = function()
    {
        if (!this.rendered)
        {
            this.render();
        }
        this.show(true);
        this.timer = null;
    }
    
    TipWindow.prototype.setDelayedActive = function(active, delayed)
    {
        if (active)
        {
            if (this.timer)
            {
                clearTimeout(this.timer);
                this.timer = null;
            }
            this.timer = setTimeout(bind(this.onSetCallback, this), delayed);
        }
        else
        {
            if (this.timer)
            {
                clearTimeout(this.timer);
                this.timer = null;
            }
            this.show(false);
        }
    }
    
    TipWindow.prototype.render = function()
    {
        removeAllDomChildren(this.dom);
        var textNode = document.createTextNode(this.content.string);
        this.dom.appendChild(textNode);
    }
    
    // the only use of this layer is to impl model dialog.
    function DialogFilterLayer(rect)
    {
        if (this instanceof DialogFilterLayer)
        {
            Base.call(this);
            this.resize(rect);
            this.setClassName("ui_model_dialog_filter_layer");
            this.dom.style.backgroundColor = 'white';
            this.dom.style.opacity = '0.25';
            this.show(false);
            this.stopMouseEventPropagation();
        }
        else
        {
            return new DialogFilterLayer(rect);
        }
    }

    inheritPrototype(DialogFilterLayer, Base);
    
    function Manager(root)
    {
        if (this instanceof Manager)
        {
            Base.call(this, root);
            // this.dom is parent dom of all other item.  Manager is a root for all ui element. 
            // and this.dom is passed from invoker. as root.

            this.filterLayer = new DialogFilterLayer(new Rect(0, 0, UI_WIDTH, UI_HEIGHT));
            this.dialogs = [];  // this is a dialog stack.
            this.unique_tip_window = new TipWindow();
            this.appendChild(this.unique_tip_window, UNIQUE_TIP_WINDOW_Z_INDEX);
            this.appendChild(this.filterLayer);
        }
        else
        {
            return new Manager(root);
        }
    }
    
    inheritPrototype(Manager, Base);
    
    Manager.prototype.showUniqueTip = function(show, tipContent, offset)
    {
        if (show)
        {
            this.unique_tip_window.setContent(tipContent);
            this.unique_tip_window.setPosition(offset);
        }
        this.unique_tip_window.setDelayedActive(show, 1000);
    }
    
    Manager.prototype.getFilterLayer = function()
    {
        return this.filterLayer;
    }
    
    Manager.prototype.appendWidget = function(widget, zIndex)
    {
        this.appendChild(widget, zIndex ? zIndex : null);
    }
    
    Manager.prototype.removeWidget = function(widget)
    {
        this.removeChild(widget);
    }
    
    Manager.prototype.updateFilterLayer = function()
    {
        if (this.dialogs.length == 0)
        {
            this.getFilterLayer().show(false);
        }
        else
        {
            // update all dialogs zIndex.
            for (var i = 0; i < this.dialogs.length; i++)
            {
                this.dialogs[i].setZIndex(MODEL_DIALOG_LAYER + (i + 1) * MODEL_DIALOG_STEP);
            }
            this.getFilterLayer().setZIndex(MODEL_DIALOG_LAYER + this.dialogs.length * MODEL_DIALOG_STEP - 1);
            this.getFilterLayer().show(true);
        }
    }
    
    Manager.prototype.showModelDialog = function(dialog, pos)
    {
        assert(dialog.parentNode == null, "dialog no parent ");
        this.dom.appendChild(dialog.getDom());
        if (pos)
        {
            dialog.setPosition(pos);
        }
        else
        {
            size = dialog.getSize();
            log.info(size);
            offset = Point.s_div(Point.s_sub((new Point(UI_WIDTH, UI_HEIGHT)), size), 2);
            log.info(new Point(UI_WIDTH, UI_HEIGHT), offset);
            dialog.setPosition(offset);
        }
        this.dialogs.push(dialog);        
        this.updateFilterLayer();
    }
    
    Manager.prototype.endModelDialog = function(dialog)
    { 
        assert(this.dialogs.length > 0, "no active model dialog. ");
        if (dialog)
        {
            this.dialogs.remove(dialog);
            dialog.detach();
        }
        else
        {
            this.dialogs.pop().detach();
        }
        this.updateFilterLayer();
    }

    // some component.
    function TextButton(text, cb)
    {
        if (this instanceof TextButton)
        {
            this.cb = cb;
        }
        else
        {
            return new TextButton(text, cb);
        }
    }
    inheritPrototype(TextButton, Base);
    
    function ImageButton(img, cb)
    {
        if (this instanceof ImageButton)
        {
            this.cb = cb;
        }
        else
        {
            return new ImageButton(img, cb);
        }    
    }
    inheritPrototype(ImageButton, Base);
    
    // here start dialog.
    function Dialog()
    {
        if (this instanceof Dialog)
        {
            Base.call(this);
            this.setClassName("ui_dialog");
            this.isRendered = false;
            this.stopMouseEventPropagation();
            this.str = null;
            this.leftButtonName = null;
            this.leftCb = null;
            this.rightButtonName = null;
            this.rightCb = null;
        }
        else
        {
            return new Dialog();
        }
    }
    inheritPrototype(Dialog, Base);
    
    Dialog.prototype.setDefaultButtonName = function(left, right)
    {
        this.leftButtonName = left;
        this.rightButtonName = right;
    }
    
    Dialog.prototype.setDefaultButtonCallback = function(left, right)
    {
        this.leftCb = left;
        this.rightCb = right;
    }
    
    Dialog.prototype.render = function()
    {
        this.renderSimpleDialog();
    }
    
    Dialog.prototype.getSize = function()
    {
        log.info(this.dom.offsetWidth, this.dom.offsetHeight);
        return new Point(this.dom.offsetWidth, this.dom.offsetHeight);
    }
    
    Dialog.prototype.renderDefaultButtons = function()
    {
        if (!this.leftButtonName)
        {
            return;
        }
        
        var yesButton = document.createElement('input');
        if (!this.rightButtonName)
        {
            yesButton.style.marginLeft = '40%';
        }
        else
        {
            yesButton.style.marginLeft = '20%';
        }
        yesButton.style.marginBottom = '20px';
        yesButton.value = this.leftButtonName;
        yesButton.type = "button";
        yesButton.style.position = "relative";
        yesButton.onclick = bind(function() {
            if (this.leftCb)
            {
                if (!this.leftCb())
                {
                    return;
                }
            }
            __manager.endModelDialog(this);
        }, this);
        this.dom.appendChild(yesButton);
        if (!this.rightButtonName)
        {
            return;
        }
        log.info(this.rightButtonName);
        var noButton = document.createElement('input');
        noButton.style.cssFloat = 'right';
        noButton.style.marginRight = '20%';
        noButton.style.marginBottom = '20px';
        noButton.value = this.rightButtonName;
        noButton.type = "button";
        noButton.style.position = "relative";
        noButton.onclick = bind(function() {
            if (this.rightCb)
            {
                if (!this.rightCb())
                {
                    return;
                }
            }
            __manager.endModelDialog(this);
        }, this);
        this.dom.appendChild(noButton);
    }
    
    Dialog.prototype.renderSimpleDialog = function()
    {
        log.info("render messageBox");
        this.dom.style.width = '350px';
        this.dom.style.backgroundColor = 'white';
        var textP = document.createElement('p');
        textP.style.margin = '30px auto';
        textP.style.marginLeft = 'auto';
        textP.style.marginRight = 'auto';
        textP.style.marginTop = '30px';
        textP.style.marginBottom = '30px';
        textP.style.width = '70%';
        textP.appendChild(document.createTextNode(this.str));
        this.dom.appendChild(textP);
        this.renderDefaultButtons();
    }
    
    Dialog.prototype.doModal = function()
    {
        if (!this.isRendered)
        {
            this.render();
            this.isRendered = false;
        }
        __manager.showModelDialog(this);
    }
    
    function MessageBox(str, okCb, cancelCb)
    {
        if (this instanceof MessageBox)
        {
            Dialog.call(this);
            this.setClassName("ui_message_box");
            this.leftCb = okCb;
            this.rightCb = cancelCb;
            this.str = str;
            this.leftButtonName = "yes";
            this.rightButtonName = "no";
        }
        else
        {
            return new MessageBox(str, okCb, cancelCb);
        }
    }
    inheritPrototype(MessageBox, Dialog);
    
    function SimpleDialogBox(msg, leftName, leftCb, rightName, rightCb)
    {
        if (this instanceof SimpleDialogBox)
        {
            Dialog.call(this);
            this.setClassName("ui_message_box");
            this.leftCb = leftCb;
            this.rightCb = rightCb;
            this.str = msg;
            if (!leftName)
            {
                this.leftButtonName = "OK";
            }
            else
            {
                this.leftButtonName = leftName;
            }
            this.rightButtonName = rightName;
        }
        else
        {
            return new SimpleDialogBox(msg, leftName, leftCb, rightName, rightCb);
        }
    }
    inheritPrototype(SimpleDialogBox, Dialog);
    
    // some helper functions.
    function getRelativePositionFromAncester(dom, ancesterDom)
    {
        var ret = new Point(0, 0);
        while(dom != ancesterDom && dom)
        {
            ret.x += dom.offsetLeft;
            ret.y += dom.offsetTop;
        }
        if (dom == ancesterDom)
        {
            return ret;
        }
        else
        {
            return new Point(0, 0);
        }
    }
    
    function getMousePointFromUIByMouseEvent(e)
    {
        return new Point(e.pageX - g_canvasOffsetX, e.pageY - g_canvasOffsetY);
    }
    
    function getDomSize(dom)
    {
        return new Point(dom.offsetWidth, dom.offsetHeight);
    }
    
    function removeAllDomChildren(dom)
    {
        while(dom.firstChild)
        {
            dom.removeChild(dom.firstChild);
        }
    }
    
    UI = new Object();
      
    UI.createUIManager = function(domRoot) {
        assert(__manager == null, "cant create global manager twice.");
       __manager = new Manager(domRoot); 
        UI.getManager = function() {return __manager;}
        UI.createUIManager = null;
    }
    
    UI.width = UI_WIDTH;
    UI.height = UI_HEIGHT;
    
    UI.ImageFormat = ImageFormat;
    UI.ImageRenderFormat = ImageRenderFormat;
    UI.makeImageRenderFormat = function(url, targetOffset, targetSize, srcOffset, srcSize)
    {
        var imgFormat = new ImageFormat(url, srcOffset, srcSize);
        return new ImageRenderFormat(imgFormat, targetOffset, targetSize);
    }
    UI.Base = Base;
    UI.ToolBar = ToolBar;
    UI.ImgToolBarItem = ImgToolBarItem;
    UI.Dialog = Dialog;
    UI.MessageBox = MessageBox;
    UI.SimpleDialogBox = SimpleDialogBox;
    
})();